package com.example.readroomforpaging3.paging

import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.readroomforpaging3.model.Article
import com.example.readroomforpaging3.network.ArticleApi
import com.example.readroomforpaging3.room.ArticleDatabase
import com.example.readroomforpaging3.room.RemoteKeys
import com.example.readroomforpaging3.viewmodel.ArticleViewModel.Companion.DEFAULT_PAGE_INDEX
import retrofit2.HttpException
import java.io.IOException
import java.io.InvalidObjectException

@ExperimentalPagingApi
class ArticleRemoteMediator(private val api: ArticleApi, private val db: ArticleDatabase) : RemoteMediator<Int, Article>() {

        override suspend fun load(loadType: LoadType, state: PagingState<Int, Article>): MediatorResult {
        val page = when (val pageKeyData = getKeyPageData(loadType, state)) {
            is MediatorResult.Success -> {
                return pageKeyData
            }
            else -> {
                pageKeyData as Int
            }
        }
        try {
            val response = api.getArticlePage(page).data.datas
            val isEndOfList = response.isEmpty()
            db.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    db.getRepoDao().clearRemoteKeys()
                    db.articleDao().clearAllArticle()
                }
                val preKey = if (page == DEFAULT_PAGE_INDEX) null else page -1
                val nextKey = if (isEndOfList) null else page + 1
                val keys = response.map {
                    RemoteKeys(repoId = it.article_id, prevKey = preKey, nextKey = nextKey)
                }
                db.getRepoDao().insertAll(keys)
                db.articleDao().insertArticles(response)
            }
            return MediatorResult.Success(endOfPaginationReached = isEndOfList)
        } catch (e: IOException) {
            return MediatorResult.Error(e)
        } catch (e: HttpException) {
            return MediatorResult.Error(e)
        }
    }

    private suspend fun getKeyPageData(loadType: LoadType, state: PagingState<Int, Article>): Any? {
        return when (loadType) {
            LoadType.REFRESH -> {
                val remoteKeys = getClosesRemoteKey(state)
                remoteKeys?.nextKey?.minus(1) ?: DEFAULT_PAGE_INDEX
            }
            LoadType.APPEND -> {
                val remoteKeys = getLastRemoteKey(state)
                        ?: throw InvalidObjectException("Remote key should not be null for $loadType")
                remoteKeys.nextKey
            }
            LoadType.PREPEND -> {
                val remoteKeys = getFirstRemoteKey(state)
                        ?: throw InvalidObjectException("Invalid state, key should not be null")
                remoteKeys.prevKey ?: return MediatorResult.Success(endOfPaginationReached = true)
                remoteKeys.prevKey
            }
        }
    }

    private suspend fun getFirstRemoteKey(state: PagingState<Int, Article>): RemoteKeys? {
        return state.pages.firstOrNull {
            it.data.isNotEmpty()
        }
                ?.data?.firstOrNull()
                ?.let { article -> db.getRepoDao().remoteKeysByRepoId(article.article_id) }
    }

    private suspend fun getLastRemoteKey(state: PagingState<Int, Article>): RemoteKeys? {
        return state.pages.lastOrNull {
            it.data.isNotEmpty()
        }?.data?.lastOrNull()
                ?.let { article -> db.getRepoDao().remoteKeysByRepoId(article.article_id) }
    }

    private suspend fun getClosesRemoteKey(state: PagingState<Int, Article>): RemoteKeys? {
        return state.anchorPosition?.let { position ->
            state.closestItemToPosition(position)?.article_id?.let { repoId ->
                db.getRepoDao().remoteKeysByRepoId(repoId)
            }
        }
    }
}